package co.edu.usbcali.ai.ga;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.vsdworld.threads.ObservableTask;

import co.edu.usbcali.ai.ga.Generation;
import co.edu.usbcali.ai.ga.bl.IChooser;
import co.edu.usbcali.ai.ga.bl.ICrosser;
import co.edu.usbcali.ai.ga.bl.IMutator;
import co.edu.usbcali.ai.ga.bl.DetTournamentChooser;
import co.edu.usbcali.ai.ga.bl.SimpleCrosser;
import co.edu.usbcali.ai.ga.bl.SimpleMutator;
import co.edu.usbcali.ai.ga.factories.IndividualFactory;

public class Ga extends ObservableTask{
	private static Logger logger=Logger.getLogger(Ga.class.getClass());
	private Breed breed;
	private int generationSize;
	private int maxRetries=20;
	private float stopValue=0;
	private int winners;
	private int mutateds;
	/**
	 * Los parametros minImprovePercent y maxToleranceCicles actual juntos, la idea es que
	 * el algoritmo genetico se detendra si en los ultimos maxToleranceCicles no se ha mejorado
	 * en más de minImprovePercent
	 */
	private float minImprovePercent=0.0f;
	private int maxToleranceCicles=10;
	private float previousMax=0;
	private int toleranceCicles=0;
	// TODO Permitir numero de hijos variable
	/**
	 * Hijos generados durante un cruce
	 */
	private int children;
	/**
	 * Número total de individuos generados
	 */
	private int totalIndividuals=0;
	private String prefix="d";
	private int generationNumber=0;
	/**
	 * Clase que determina como se escogeran los sobrevivientes
	 */
	private IChooser chooser;
	/**
	 * Clase que determina como se realizaran los cruces entre individuos
	 */
	private ICrosser crosser;
	/**
	 * Clase que determina como se mutara un individuo
	 */
	private IMutator mutator;

	private Generation generation;
	
	/* **********************************************
	 * CONSTRUCTORS
	 * **********************************************/
	
	
	/**
	 * @param generationSize
	 * @param mutateds
	 * @param breed
	 * @param winners
	 */
	public Ga( Breed breed, int generationSize, int winners,int children, int mutateds) {
		super();
		this.setName(Ga.class.getSimpleName());
		this.setTotalWork(1100);
		this.generationSize = generationSize;
		this.mutateds = mutateds;
		this.breed = breed;
		this.winners = winners;
		this.children = children;
	}

	/* **********************************************
	 * 
	 * **********************************************/


	public void run(){
		beginTask(getTaskName(), getTotalWork());
		String message=Messages.getInstance().getMessage2(Messages.USB_GA_0000, Messages.PRM_GENERATION_SIZE+Messages.SEP+generationSize+Messages.SEP+Messages.PRM_WINNERS+Messages.SEP+winners+Messages.SEP+Messages.PRM_MUTATED+Messages.SEP+mutateds+
				  Messages.SEP+Messages.PRM_CHILDREN+Messages.SEP+children+Messages.SEP+Messages.PRM_BREED+Messages.SEP+breed.getName()+Messages.SEP+Messages.PRM_VALUE+Messages.SEP+stopValue);
		logger.debug(message);
		if (chooser==null){
			chooser=DetTournamentChooser.getInstance();
		}
		if(crosser==null){
			crosser=SimpleCrosser.getInstance();
		}
		if(mutator==null){
			mutator=SimpleMutator.getInstance();
		}
		worked(10);
		try {
			if(generation==null){
			generation = createInitialGeneration(breed, generationSize);
			message=Messages.getInstance().getMessage2(Messages.USB_GA_0001,Messages.PRM_GENERATION_NUMBER+Messages.SEP+generation.getNumber());
			subTask(message);
			logger.info(message);

			message=Messages.getInstance().getMessage2(Messages.USB_GA_0002,Messages.PRM_GENERATION_NUMBER+Messages.SEP+generation.getNumber());
			subTask(message);
			logger.info(message);
			generation.evaluate(breed);
			}
			totalIndividuals+=generation.getSize();
			message=Messages.getInstance().getMessage2(Messages.USB_GA_0003,Messages.PRM_TOTAL_INDIVIDUALS+Messages.SEP+totalIndividuals);
			logger.info(message);
			logger.info("Generación "+generation.getNumber()+":"+generation.getList());
//			while (generation.getMax()<stopValue){
			while (!finish(generation)){
				message=Messages.getInstance().getMessage2(Messages.USB_GA_0004, Messages.PRM_GENERATION_NUMBER+Messages.SEP+generation.getNumber()+Messages.SEP+Messages.PRM_VALUE+Messages.SEP+Float.toString(generation.getMax()));
				subTask(message);
				logger.info(message);
//				System.out.println("Generación:"+generation.getNumber()+"Mejor calificación:"+Float.toString(generation.getMax()));
				List<Individual> winnersList=chooser.choose(generation.getList(), winners);
				logger.info("winners: "+winnersList);
				//			totalIndividuals+=winnersList.size();
				List<Individual> childrenList=crosser.cross(winnersList, children,generation.getNumber(),breed.getCromosomClass(),this);
				logger.info("children: "+childrenList);
				totalIndividuals+=childrenList.size();
//				System.out.println("Winner list:"+winnersList);
//				System.out.println("ChildrenList:"+childrenList);
//				System.out.println("W:"+Generation.getFenotypesList(winnersList, race));
//				System.out.println("C:"+Generation.getFenotypesList(childrenList, race));
				List<Individual> mutatedList=mutator.mutate(winnersList,childrenList, mutateds,generation.getNumber(),breed.getCromosomClass(),this);
				logger.info("mutated: "+mutatedList);
				totalIndividuals+=mutatedList.size();
				List<Individual> newGenerationList=new ArrayList<Individual>(winnersList);
				newGenerationList.addAll(childrenList);
				newGenerationList.addAll(mutatedList);
				//			con++;
				generation=new Generation(generationSize);
				generation.setList(newGenerationList);
				generation.evaluate(breed);
				generation.serialize();
				logger.info(generation);
			}
			long fullPopulation=breed.getCromosomType().getFullPopulation();
			List<Individual> successfulls=generation.successfulIndividuals(stopValue);
			if (successfulls.size()>0){
//			System.out.print("Individuos solucion:"+generation.successfulIndividuals(stopValue)+" total individuos generados:" + totalIndividuals+" de "+fullPopulation+" posibles.");
			System.out.print("Individuos solución:");
			for (Individual individual : successfulls) {
				System.out.println("Individuo:"+individual.getId()+" cromosoma:"+individual.getCromosom().toString()+" fenotipo:"+breed.getFenotype(individual));
			}
			}else{
				System.out.print("Individuo solucion:"+generation.maxIndividual()+" total individuos generados:" + totalIndividuals+" de "+fullPopulation+" posibles.");				
			}
			System.out.println(" total individuos generados:" + totalIndividuals+" de "+fullPopulation+" posibles.");
			float percent=(totalIndividuals*100)/fullPopulation;
			System.out.print("Porcentaje:"+percent+"%");
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public boolean finish(Generation generation){
		float max=generation.getMax();
		if (max>=stopValue){
			return true;
		}
		if(previousMax==0){
			previousMax=max;
			return false;
		}else if (((max/previousMax)-1)<minImprovePercent){
			toleranceCicles++;
		}else{
			toleranceCicles=0;
			previousMax=max;
		}
		if(previousMax<max){
			previousMax=max;
		}
		if (toleranceCicles>maxToleranceCicles){
			return true;
		}
		return false;
	}
	
	public Generation createInitialGeneration(Breed p_breed,int p_quantity) throws InstantiationException, IllegalAccessException{
		Generation generation=new Generation(getGenerationNumber());
		for(int con=0;con<p_quantity;con++){
			Individual individual=IndividualFactory.createAleatoryIndividual(getNextPrefix(),p_breed);
			individual.setBirthGeneration(getGenerationNumber());
			if (generation.getList().contains(individual)){
				con--;
			}else{
				generation.getList().add(individual);
			}
		}
		return generation;
	}

	
	public String getNextPrefix(){
		return getPrefix()+getNextId();
	}

	public synchronized int getNextId() {
		int ret=totalIndividuals;
		totalIndividuals++;
		return ret;
	}

	/* **********************************************
	 * GETTERS AND SETTERS
	 * **********************************************/
	
	public Breed getRace() {
		return breed;
	}
	public void setRace(Breed race) {
		this.breed = race;
	}
	public int getGenerationSize() {
		return generationSize;
	}
	public void setGenerationSize(int generationSize) {
		this.generationSize = generationSize;
	}
	public int getMaxRetries() {
		return maxRetries;
	}
	public void setMaxRetries(int maxRetries) {
		this.maxRetries = maxRetries;
	}
	public float getStopValue() {
		return stopValue;
	}
	public void setStopValue(float stopValue) {
		this.stopValue = stopValue;
	}
	public int getWinners() {
		return winners;
	}
	public void setWinners(int winners) {
		this.winners = winners;
	}
	public int getMutateds() {
		return mutateds;
	}
	public void setMutateds(int mutateds) {
		this.mutateds = mutateds;
	}
	public float getMinImprovePercent() {
		return minImprovePercent;
	}

	public void setMinImprovePercent(float minImprovePercent) {
		this.minImprovePercent = minImprovePercent;
	}

	public int getMaxToleranceCicles() {
		return maxToleranceCicles;
	}

	public void setMaxToleranceCicles(int maxToleranceCicles) {
		this.maxToleranceCicles = maxToleranceCicles;
	}

	public int getChildren() {
		return children;
	}
	public void setChildren(int children) {
		this.children = children;
	}
	public int getTotalIndividuals() {
		return totalIndividuals;
	}
	public void setTotalIndividuals(int totalIndividuals) {
		this.totalIndividuals = totalIndividuals;
	}
	public String getPrefix() {
		return prefix;
	}
	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}
	public int getGenerationNumber() {
		return generationNumber;
	}
	public void setGenerationNumber(int generationNumber) {
		this.generationNumber = generationNumber;
	}
	public IChooser getChooser() {
		return chooser;
	}

	public void setChooser(IChooser chooser) {
		this.chooser = chooser;
	}

	public ICrosser getCrosser() {
		return crosser;
	}
	public void setCrosser(ICrosser crosser) {
		this.crosser = crosser;
	}

	public IMutator getMutator() {
		return mutator;
	}

	public void setMutator(IMutator mutator) {
		this.mutator = mutator;
	}

	public Generation getGeneration() {
		return generation;
	}

	public void setGeneration(Generation generation) {
		this.generation = generation;
	}

	
}
